/*
    怎样保证多线程访问即保证并发度又保证线程安全？
    像之前的阻塞队列，如果把它整体使用，就加锁使用。
    如果资源允许局部性的被多个线程并发访问，你能被拆成几份资源，就允许几个线程进入！
    所以为了更好的保护资源，引入了信号量！

    信号量：共享资源内部可以被拆成多份！由信号量决定最多有几个线程！本质：计数器！计数器的本质：临界资源的数量，把资源是否就绪放在了临界区之外！（就是不用在临界区内判断了）
    p()（--） ->  访问操作  ->  v()（++）
    所以在pv操作之间不需要判断条件了。
    申请信号量的本质：间接的已经在判断了。
    p关注什么资源呢？关注环形队列还有多少剩余空间（spaceSem = N:环形队列N个元素）
    c关注什么资源呢？关注环形队列还有多少剩余数据 (DataSem = 0：开始无数据)

    生产者：P（spaceSem):空间资源--    ->生产  ->   V(DataSem):数据资源++
    消费者：P（DataSem）: 数据资源--   ->消费  ->   V(spaceSem):空间资源++
    刚开始为空：一定是生产者先执行！
    信号量本身也是原子的！
*/
#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>

template<class T>
class BlockQueue
{
public:
    BlockQueue(int maxcap = defaultnum)
    :_maxcap(maxcap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
        pthread_cond_init(&_p_cond, nullptr);

        _low_water = _maxcap / 3;  // 6
        _high_water = _maxcap * 2 / 3;  // 13
    }

    void push(const T& data)
    {
        pthread_mutex_lock(&_mutex);

        // 判断的时候要防止线程被伪唤醒的情况,将if改为while
        // if(_q.size() == _maxcap)  // 不能你想生产就生产呀！你得先确保生产条件满足
        while(_q.size() == _maxcap)
        {
            // 让当前执行push的线程去条件变量下等待。

            // 资源不就绪，就等待。是持有锁等待的！持有锁等待会导致其他人也申请不到锁。因此要自动释放
            // 因为唤醒而返回的时候，重新持有锁！
            pthread_cond_wait(&_p_cond,&_mutex); // 1、调用的时候自动释放锁  2、处于阻塞状态

            // 如果线程wait时，被误唤醒了呢？多个线程会存在这种可能（伪唤醒），此时队列已经满了，在继续push的时候就有问题了。

        }
        // 1、队列没满
        // 2、被唤醒
        _q.push(data);    // 生产者生产完数据就能确保有数据，此时就可以唤醒消费者
        pthread_cond_signal(&_c_cond);       

        // if(_q.size() > _high_water)
        // {
        //     pthread_cond_signal(&_c_cond);       
        // }
        pthread_mutex_unlock(&_mutex);
    }

    T pop()
    {
        // 这里为什么要先加锁？再判断？
        // 因为判断临界资源条件是否满足，也是在访问临界资源！
        pthread_mutex_lock(&_mutex);
        while(_q.size() == 0)
        {
            pthread_cond_wait(&_c_cond, &_mutex);
        }
        T data = _q.front();
        _q.pop();   // 消费者消费一个一定能保证当前一定有空间让生产者进行生产。
        // pthread_cond_signal(&_p_cond);

        if(_q.size() < _low_water)
        {
            pthread_cond_signal(&_p_cond);
        }
        pthread_mutex_unlock(&_mutex);
        return data;
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_c_cond);
        pthread_cond_destroy(&_p_cond);
    }
private:
    std::queue<T> _q; 
    int _maxcap;   // 极值
    pthread_mutex_t _mutex;
    pthread_cond_t _c_cond;
    pthread_cond_t _p_cond;
    // int _mincap;   // 极值
    static const int defaultnum = 20;

    int _low_water;
    int _high_water;
};